import Dep from "./Dep"
import { arrayMethods } from "./Array"

function defineReactive(data, key, val) {
  if (typeof val === 'object') new Observe(val)
  let childOb = observe(val)
  let dep = new Dep()
  Object.defineProperty(data, key, {
    enumerable: true,
    configurable: true,
    get() {
      dep.depend()
      // 收集Array依赖
      if (childOb) {
        childOb.dep.depend()
      }
      return val
    },
    set(newVal) {
      if (val === newVal) return
      val = newVal
      dep.notify()
    }
  })
}

// __proto__是否可用
const hasProto = '__proto__' in {}
const arrayKeys = Object.getOwnPropertyNames(arrayMethods)

export class Observe {
  constructor(value) {
    this.value = value

    this.dep = new Dep()

    if (Array.isArray(value)) {
      const augment = hasProto ? protoAugment : copyAugment
      augment(value, arrayMethods, arrayKeys)
    } else {
      this.walk(value)
    }
  }

  walk(obj) {
    const keys = Object.keys(obj)
    for (let i = 0; i < keys.length; i++) {
      defineReactive(obj, keys[i], obj[keys[i]])
    }
  }
}

/**
 * 为value创建Observe实例
 * 如果创建成功，直接返回新创建的observe实例
 * 如果value已经存在一个observe实例，则直接返回它
 * @param {*} value 
 * @param {*} asRootData 
 */
function observe(value, asRootData) {
  if (!isObject(value)) return
  let ob
  if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observe) {
    ob = value.__ob__
  } else {
    ob = new Observe(value)
  }
  return ob
}

function protoAugment(target, src, keys) {
  target.__proto__ = src
}

function copyAugment(target, src, keys) {
  for (let i = 0, l = keys.length; i < l; i++) {
    const key = keys[i]
    def(target, key, src[key])
  }
}